package com.gz.common.mongodb.support;

import com.gz.common.mongodb.enums.MongoGroupEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import static com.gz.common.mongodb.enums.MongoGroupEnum.*;

/**
 * @ClassName MongoGroupMapper
 * @Description // mongo分组函数
 * @Author WHX
 * @Date 2021/8/23 16:31
 **/
public class MongoGroup {
    /**
     * 聚合函数暂存容器
     */
    private final List<MongoGroupFunction> mongoGroupFunctions = new ArrayList<>();
    /**
     * 返回字段暂存容器
     */
    private final Set<String> projectionAndIncludes = new TreeSet<>();

    /**
     * 聚合函数
     * AggregationOperation
     */
    @Getter
    private GroupOperation group;
    /**
     * 返回字段
     * AggregationOperation
     */
    @Getter
    private ProjectionOperation projection;

    public static MongoGroup init() {
        return new MongoGroup();
    }

    public MongoGroup count(String asField) {
        addFuncAndIncludes("null", asField, count);
        return this;
    }

    public MongoGroup sum(String column) {
        return this.sum(column, column);
    }

    public MongoGroup sum(String column, String asField) {
        addFuncAndIncludes(column, asField, sum);
        return this;
    }

    public MongoGroup avg(String column) {
        return this.avg(column, column);
    }

    public MongoGroup avg(String column, String asField) {
        addFuncAndIncludes(column, asField, avg);
        return this;
    }

    public MongoGroup min(String column) {
        return this.min(column, column);
    }

    public MongoGroup min(String column, String asField) {
        addFuncAndIncludes(column, asField, min);
        return this;
    }

    public MongoGroup max(String column) {
        return this.max(column, column);
    }

    public MongoGroup max(String column, String asField) {
        addFuncAndIncludes(column, asField, max);
        return this;
    }

    public MongoGroup first(String column ) {
        return this.first(column, column);
    }

    public MongoGroup first(String column, String asField) {
        addFuncAndIncludes(column, asField, first);
        return this;
    }

    public MongoGroup last(String column) {
        return this.last(column, column);
    }

    public MongoGroup last(String column, String asField) {
        addFuncAndIncludes(column, asField, last);
        return this;
    }

    public MongoGroup push(String column) {
        return this.push(column, column);
    }

    public MongoGroup push(String column, String asField) {
        addFuncAndIncludes(column, asField, push);
        return this;
    }

    public MongoGroup stdDevSamp(String column) {
        return this.stdDevSamp(column, column);
    }

    public MongoGroup stdDevSamp(String column, String asField) {
        addFuncAndIncludes(column, asField, stdDevSamp);
        return this;
    }

    public MongoGroup stdDevPop(String column) {
        return this.stdDevPop(column, column);
    }

    public MongoGroup stdDevPop(String column, String asField) {
        addFuncAndIncludes(column, asField, stdDevPop);
        return this;
    }

    /**
     * 构建聚合查询对象 AggregationOperation
     * @param groupcolumn 分组字段
     */
    public void build(List<String> groupcolumn) {
        // 分组字段
        String[] strings = groupcolumn.toArray(new String[groupcolumn.size()]);
        group = Aggregation.group(strings);
        projection = Aggregation.project(strings);

        for (MongoGroupFunction func : this.mongoGroupFunctions) {
            String column = func.getColumn();
            String asField = func.getAsField();
            switch (func.getMongoGroupEnum()) {
                case count:
                    group = group.count().as(asField);
                    break;
                case sum:
                    group = group.sum(column).as(asField);
                    break;
                case avg:
                    group = group.avg(column).as(asField);
                    break;
                case min:
                    group = group.min(column).as(asField);
                    break;
                case max:
                    group = group.max(column).as(asField);
                    break;
                case first:
                    group = group.first(column).as(asField);
                    break;
                case last:
                    group = group.last(column).as(asField);
                    break;
                case push:
                    group = group.push(column).as(asField);
                    break;
                case stdDevSamp:
                    group = group.stdDevSamp(column).as(asField);
                    break;
                case stdDevPop:
                    group = group.stdDevPop(column).as(asField);
                    break;
                default:
                    break;
            }
        }
        for (String asField : projectionAndIncludes) {
            projection = projection.andInclude(asField);
        }
        // 当分组字段只有一个时，会把该分组字段值映射到id，而该分组字段返回null，以下处理是为了让该分组字段值正确的映射到该字段本身；
        if (groupcolumn.size() == 1) projection = projection.and(groupcolumn.get(0)).previousOperation();
    }

    /**
     * 添加聚合函数，同时添加返回字段
     * @param column 聚合字段
     * @param asField 映射字段
     * @param mongoGroupEnum 聚合函数
     */
    public void addFuncAndIncludes(String column, String asField, MongoGroupEnum mongoGroupEnum) {
        assertStrHasText(column, asField);
        mongoGroupFunctions.add(new MongoGroupFunction(column, asField, mongoGroupEnum));
        projectionAndIncludes.add(asField);
    }

    /**
     * 校验全部必须为非空字符
     *
     * @param strs
     */
    public void assertStrHasText(String... strs) {
        for (String str : strs) {
            Assert.isTrue(StringUtils.hasText(str));
        }
    }

    @Getter
    @AllArgsConstructor
    public static class MongoGroupFunction {
        private String column;
        private String asField;
        private MongoGroupEnum mongoGroupEnum;
    }


}
